import { defineGlobalProperty, injectRoomPrototype, interval, isMyRoom, log, roomLog } from '@/ayaka'
import {
    AdaptionJudgeFn,
    AdaptionMemoryFn,
    CreepDefaultPriority,
    CreepRoles,
    SpawnTask,
    defaultAdaptionJudge,
} from './types'
import StructureCache from '@/modules/structureCache'
import { BodyPartArrayType, adaptiveBody, calcCreepCost, flatBodyParts } from '@/utils/common'
import { createCreepName } from './creepName'
import DefenseTaskManager from '@/room/task/base/defense'
import { CreepBoostData } from '../lab/types'
import { ResourcePartMap } from '@/utils/constants'

/**
 * spawn 控制器
 */
export default class SpawnController {
    /**
     * 最大任务数
     */
    public static readonly maxTasks = 15

    /**
     * 自适应判断函数，判断是否需要自适应
     */
    public static adaptionJudgeFns: { [role in CreepRoles]?: AdaptionJudgeFn } = {}

    /**
     * TODO: 自适应后需要增加的 memory，因为一些 memory 需要根据体型来计算，但是自适应后体型会变化
     */
    public static adaptionMemoryFns: { [role in CreepRoles]?: AdaptionMemoryFn } = {}

    /**
     * 暂缓孵化房间，此时最后一个 spawn 只会孵化搬运工和战斗爬，便于战斗爬去 renew
     */
    public static postponeSpawnRoom = new Map<string, number>()

    /**
     * 不可实例化
     */
    protected constructor() {
        throw new Error('SpawnController 不可被实例化！')
    }

    /**
     * 获取任务队列
     */
    @injectRoomPrototype
    public static getSpawnTasks(room: Room) {
        if (!room.memory.spawnTasks) {
            room.memory.spawnTasks = []
        }
        return room.memory.spawnTasks
    }

    /**
     * 孵化控制器工作
     */
    @interval(3)
    public static run(room: MyRoom) {
        const tasks = SpawnController.getSpawnTasks(room)
        if (!tasks.length) return

        // 至少满300才孵化
        const energyAvailable = room.energyAvailable
        if (energyAvailable < 300) return

        const spawns = SpawnController.getSpawn(room)
        if (!spawns.length) return

        if (DefenseTaskManager.hasNukeLand(room, Game.time + 150)) {
            return
        }

        // 是否是暂缓孵化模式
        const isPostpone = SpawnController.isPostponeSpawnRoom(room.name)
        // 要暂缓的 spawn
        const postponeSpawn = isPostpone ? SpawnController.getPostponeSpawn(room) : undefined

        for (const spawn of spawns) {
            if (spawn.spawning) continue
            // isActive 的 cpu 消耗有点大，八级时没有必要用
            if (room.controller.level < 8 && !spawn.isActive()) continue

            const task = tasks[0]
            // 启动暂缓孵化模式时，最后一个 spawn 不孵化优先级低于战斗爬的爬
            if (
                postponeSpawn === spawn &&
                (!task.priority || task.priority > CreepDefaultPriority[CreepRoles.teamMember])
            ) {
                continue
            }

            let bodyParts = flatBodyParts(task.body)

            const energyRequired = calcCreepCost(bodyParts)
            const isEnengyEnough = energyAvailable >= energyRequired

            const adaptionFunc = task.defaultAdaption
                ? defaultAdaptionJudge
                : SpawnController.adaptionJudgeFns[task.role]
            // 能量不够又无法自适应就跳过
            if (!isEnengyEnough && !task.adaption && !adaptionFunc?.(room)) {
                // 不能自适应且超出能量上限就删除该任务
                if (!adaptionFunc && energyRequired > room.energyCapacityAvailable) {
                    tasks.shift()
                    roomLog(room, `${task.role} ${task.body}能量不足，已移除任务，请重新发布`, '', 'red')
                }
                return
            }

            const creepName = task.creepName || createCreepName(task)

            const extraMemory: CreepMemory = {
                role: task.role,
                belong: room.name,
                shard: Game.shard.name,
                ...task.memory,
            }

            if (!isEnengyEnough) {
                bodyParts = adaptiveBody(bodyParts, energyAvailable)
            }

            // boost 检查，如果需要 boost，确保 boost 信息中需要的部件的数量不大于 bodyParts 中的数量
            if (extraMemory.boost) {
                for (const part in extraMemory.boost.data) {
                    const partBoostInfo = extraMemory.boost.data[part as BodyPartConstant]!
                    const actualPartNumber = bodyParts.filter((p) => p === part).length
                    if (partBoostInfo.require > actualPartNumber) {
                        partBoostInfo.require = actualPartNumber
                    }
                }
            }

            const result = spawn.spawnCreep(bodyParts, creepName, {
                memory: extraMemory,
            })

            if (result === OK) {
                tasks.shift()
                roomLog(room, `${creepName}孵化成功`, '', 'green')
                return
            }

            if (result === ERR_NAME_EXISTS) {
                tasks.shift()
                roomLog(room, `${creepName}已存在，已移除任务，请重新发布`, '', 'red')
                return
            }
        }
    }

    /**
     * 按爬名搜索孵化任务
     */
    @injectRoomPrototype
    public static searchSpawnTaskByCreepName(room: Room, creepName: string) {
        const tasks = SpawnController.getSpawnTasks(room)
        return tasks.find((task) => task.creepName === creepName)
    }

    /**
     * 按角色名搜索孵化任务
     */
    @injectRoomPrototype
    public static searchSpawnTaskByRole(room: Room, role: CreepRoles) {
        const tasks = SpawnController.getSpawnTasks(room)
        return tasks.filter((task) => task.role === role)
    }

    /**
     * 添加孵化任务
     */
    @injectRoomPrototype
    public static addSpawnTask(room: Room, task: SpawnTask) {
        if (!task.creepName) {
            task.creepName = createCreepName(task)
        }
        if (SpawnController.searchSpawnTaskByCreepName(room, task.creepName)) return false

        const tasks = SpawnController.getSpawnTasks(room)
        if (tasks.length >= SpawnController.maxTasks) return false
        if (!task.priority) task.priority = CreepDefaultPriority[task.role]
        // 在孵化列表中找到第一个优先级低于该任务的任务，然后插到它的前面
        const index = tasks.findIndex((t) => (t.priority || 10) > (task.priority || 10))
        if (~index) {
            tasks.splice(index, 0, task)
        } else {
            // 如果没有找到，就插到最后
            tasks.push(task)
        }
        return task.creepName
    }

    /**
     * 移除孵化任务
     */
    @injectRoomPrototype
    public static removeSpawnTask(room: Room, task: SpawnTask) {
        const tasks = SpawnController.getSpawnTasks(room)
        const index = tasks.findIndex((t) => t === task)
        if (~index) {
            tasks.splice(index, 1)
            return true
        }
        return false
    }

    /**
     * 获取spawn
     */
    @injectRoomPrototype
    public static getSpawn(room: Room) {
        return StructureCache.getStructuresByType(room, STRUCTURE_SPAWN)
    }

    /**
     * 注册自适应判断函数
     */
    public static registerAdaptionJudge(role: CreepRoles, fn: AdaptionJudgeFn) {
        SpawnController.adaptionJudgeFns[role] = fn
    }

    /**
     * 孵化过程可视化
     */
    public static visual(creep: Creep) {
        if (!creep.spawning) return
        creep.room.visual.text(`${creep.memory.role}`, creep.pos.x, creep.pos.y, {
            color: '#ffffff',
            font: 0.5,
            align: 'center',
            stroke: '#ff9900',
        })
    }

    /**
     * 获取用于暂缓孵化的 spawn
     */
    public static getPostponeSpawn(room: Room): StructureSpawn | undefined {
        const spawns = SpawnController.getSpawn(room)
            .sort((a, b) => a.pos.hashCodeInRoom() - b.pos.hashCodeInRoom())
            .filter((spawn) => spawn.isActive())
        return spawns[spawns.length - 1]
    }

    /**
     * 判断房间是否是暂缓孵化模式
     */
    public static isPostponeSpawnRoom(roomName: string) {
        const tick = SpawnController.postponeSpawnRoom.get(roomName)
        if (!tick || tick <= 0) {
            return false
        }

        SpawnController.postponeSpawnRoom.set(roomName, tick - 1)
        return true
    }

    /**
     * 添加暂缓孵化房间
     */
    public static addPostponeSpawnRoom(roomName: string, tick = 5) {
        SpawnController.postponeSpawnRoom.set(roomName, tick)
    }

    /**
     * 移除暂缓孵化房间
     */
    public static removePostponeSpawnRoom(roomName: string) {
        SpawnController.postponeSpawnRoom.delete(roomName)
    }

    /**
     * TODO: 统计房间中所有爬的孵化时间总和，这种统计方法有问题，当爬不是正常死亡就不会被统计进去，待优化
     */
    @injectRoomPrototype
    public static getSpawnTime(room: Room) {
        return Object.values(Game.creeps).reduce(
            (total, creep) => total + (creep.memory.belong === room.name ? creep.body.length * CREEP_SPAWN_TIME : 0),
            0,
        )
    }
}

/**
 * 获取存在的爬，并清除过期爬
 */
export const getCreeps = (room: Room, creepNames: string[]) => {
    const creeps: Creep[] = []
    creepNames?.forEach((creepName, index) => {
        const creep = Game.creeps[creepName]
        if (!creep && !SpawnController.searchSpawnTaskByCreepName(room, creepName)) {
            creepNames.splice(index, 1)
        }

        if (creep) {
            creeps.push(creep)
        }
    })
    return creeps
}

defineGlobalProperty('SpawnTask', {
    value: {
        add(
            roomName: string,
            role: CreepRoles,
            body: BodyPartArrayType,
            priority = 26,
            boostResource: MineralBoostConstant[] = [],
            memory: Partial<CreepMemory> = {},
        ) {
            const room = Game.rooms[roomName]
            if (!room || !isMyRoom(room)) {
                log(`找不到房间 ${roomName}，无法添加孵化任务`, ['Spawn'], 'red')
                return
            }

            if (boostResource?.length) {
                const boostData: CreepBoostData = {}
                for (const res of boostResource) {
                    boostData[ResourcePartMap[res]] = {
                        type: res,
                        require: 50,
                        amount: 0,
                    }
                }
                memory.boost = {
                    data: boostData,
                }
            }

            const task: SpawnTask = { role, body, priority, memory }

            SpawnController.addSpawnTask(room, task)
        },
    },
})
